"""A proxy to networkx.

Current version delegates to networkx.XGraph with integer nodes (vertices).
An internal dictionary is maintained to support vertex weights.
Edge weights are supported by networkx.XGraph.

You should write an implementation of your own, independant from networkx."""

import networkx as nx
import copy #"Generic (shallow and deep) copying operations" package.

class EdgeData(dict):
    """An implementation of a edge data class, which serves for storing
    additional edge properties (such as weight, capacity, etc)."""
    def __init__(self, bandwidth = 1, defaults = None):
        super(dict, self).__init__()
        self["bandwidth"] = bandwidth
        if defaults is None:
            defaults = {}
        self.update(defaults)

    def setProperty(self,key,value):
        self[key] = value

    def getProperty(self,key):
        return self[key]

    def getBandwidth(self):
        return self["bandwidth"]

    def setBandwidth(self,value):
        self["bandwidth"] = value

class VertexData(dict):
    """An implementation of a vertex data class, which serves for storing
    additional vertex properties (such as weight, tags, etc)."""
    
    def __init__(self, label = '',defaults = None):    
        super(dict, self).__init__()
        self["label"] = label
        if defaults is None:
            defaults = {}
        self.update(defaults)

    def setProperty(self,key,value):
        self[key] = value

    def getProperty(self,key):
        return self[key]

    def setLabel(self,label):
        self["label"] = label

    def getLabel(self):
        return self["label"]


class Edge():
    """An implementation of an edge class which includes a weight payload."""
    
    def __init__(self,graph,v0,v1,weight = None):
        assert isinstance(graph,Graph)
        self._G = graph
        self._v0 = v0
        self._v1 = v1
        if weight is None:
            self._weight=EdgeData()
        else:
            self._weight = weight

    def getWeight(self):
        return self._weight

    def getV0(self):
        return self._G.getVertex(self._v0)

    def getV1(self):
        return self._G.getVertex(self._v1)

class Graph():
    """An implementation of a Graph ADT. While this implementation
    delegates everything to networkx, you should write your own
    implementations."""
    
    def __init__(self,copyFrom = None):

        self._G = nx.Graph()
        self.vertexWeights = {}

        if isinstance(copyFrom,Graph):
            self.vertexWeights = copyFrom.vertexWeights
            self._G = copyFrom._G

        if isinstance(copyFrom,nx.Graph):
            for v in copyFrom.nodes_iter():
                self.addVertex(v,VertexData())
            for e in copyFrom.edges_iter():
                self.addEdge(*e)
    
    def addVertex(self,index,weight = None):
        if weight is None:
            weight = VertexData()
        
        self._G.add_node(index)
        self.vertexWeights[index] = weight

    def addEdge(self,v1,v2,weight = None):
        if weight is None:
            weight = EdgeData()
        self._G.add_edge(v1,v2,weight)

    def deleteEdge(self,e):
        assert isinstance(e,Edge)
        self._G.remove_edge(e.getV0().getNumber(), e.getV1().getNumber())

    def getNumberOfVertices(self):
        return self._G.number_of_nodes()

    def getNumberOfEdges(self):
        return self._G.number_of_edges()

    def getVertexWeight(self,index):
        return self.vertexWeights[index]

    def getVertices(self):
        return self._G.nodes_iter()

    def isEdge(self,v1,v2):
        return self._G.has_edge(v1,v2)

    def getEdge(self,v1,v2):
        return Edge(self, v1, v2, self._G.get_edge_data(v1,v2))

    def getEdges(self):
        return [Edge(self, e[0], e[1], self._G.get_edge_data(e[0],e[1])) for e in self._G.edges_iter()]

    def getNeighbors(self, v):        
        return [Edge(self, v, u, self._G.get_edge_data(v,u)) for u in self._G.neighbors_iter(v)]
